Skip to content

feat(triggers): per-trigger Claude --resume continuation across webhooks#1

Open
lsoldado wants to merge 1 commit into
developfrom
feat/clickup-session-resume
Open

feat(triggers): per-trigger Claude --resume continuation across webhooks#1
lsoldado wants to merge 1 commit into
developfrom
feat/clickup-session-resume

Conversation

@lsoldado
Copy link
Copy Markdown
Owner

@lsoldado lsoldado commented May 3, 2026

Summary

Adds opt-in claude --resume continuation for consecutive webhooks of the same logical thread.

Why: incident 2026-05-02 ClickUp task 86c9kyquv (Vitalmadente). User asked for a marketing report ($3.21, 22min). 30min later asked to "implement recommendation 3". Fresh follow-up Oracle had to re-read the entire Doc, re-derive context, re-fetch GA4/GBP/Ads — wasted $2.19 of re-derivation that the prior Oracle had cached.

Fix: per-trigger opt-in --resume. Follow-up Oracles inherit the full conversation history with full reasoning trace.

Changes

  • triggers.resume_sessions BOOLEAN column (default false)
  • trigger_session_threads table mapping (trigger_id, dedup_key) → claude_session_id
  • _extract_dedup_key() per-source: ClickUp task.id, GitHub PR/issue number, Linear issue.id
  • run_claude(..., resume_session_id=...) passes --resume <id> to CLI
  • Auto-retry without resume on stale session
  • Settings UI: new Sessions tab + per-trigger checkbox

Endpoints

  • GET/PUT /api/settings/sessions
  • GET /api/sessions, DELETE /api/sessions/<id>, POST /api/sessions/cleanup-stale

Schema (Alembic migration 0012)

Reversible. Idempotent (_has_column/_has_table checks).

🤖 Generated with Claude Code

…uation

Adds opt-in `claude --resume` continuation across consecutive webhooks
of the same logical thread (ClickUp task, GitHub PR, Linear issue).
Without this, every webhook spawned a fresh Claude subprocess that had
to re-read all prior comments, re-derive context, and re-fetch data via
API calls — losing 90%+ of the model's reasoning trace between turns.

## Why

Real-world incident driving this: 2026-05-02 ClickUp task 86c9kyquv.
User asked the Oracle for a marketing report (Phase 1, ~$3.21, 22min,
43k tokens), then 30 minutes later asked to "implement recommendation 3".
The fresh follow-up Oracle had to:
  - Re-read the entire Google Doc via API call
  - Re-derive what "rec 3" meant from raw text
  - Re-discover the cuid Vitalmadente
  - Re-fetch GA4/GBP/Ads data baseline
  - LOSE the entire reasoning trace (rejected hypotheses, alternatives
    considered) that the prior Oracle had in context

With session resume, the second Oracle inherits the full conversation
history — knows what was tried, what was rejected, and why — at zero
re-derivation cost.

## How — backend

- `Trigger.resume_sessions BOOLEAN` column (default false). Existing
  triggers keep current "fresh subprocess" behaviour. Operator opts in
  per-trigger via dashboard checkbox or YAML.
- `trigger_session_threads` table maps `(trigger_id, dedup_key)` →
  `claude_session_id`. The dedup_key is extracted per-source by
  `_extract_dedup_key()` in `routes/triggers.py`:
    - ClickUp (source=custom + slug contains 'clickup'): task.id
    - GitHub: pull_request.number / issue.number
    - Linear: issue.id
  Extensible for new sources.
- `run_claude(..., resume_session_id=...)` in `ADWs/runner.py`
  prepends `--resume <id>` to the CLI invocation. Captures `session_id`
  from output JSON and returns it for upsert. Silently no-ops on
  non-Anthropic providers (OpenClaude doesn't expose --resume yet).
- `_execute_trigger` flow:
  1. If `trigger.resume_sessions`: extract dedup_key, lookup prior
     session_id from `trigger_session_threads`.
  2. Pass to `run_claude` (None = fresh).
  3. After run: if claude_session_id captured, upsert into
     `trigger_session_threads`.
  4. If resume failed (stale session): retry once without resume.

## How — UI

- `/settings → Sessions` tab (new):
  * Default-on toggle for new triggers
  * Auto-cleanup window (1-365 days) + daily cleanup hour (0-23)
  * Force compaction turn count (1-500)
  * Storage stats (count + disk usage of `~/.claude/projects/`)
  * Live list of active threads with manual reset + bulk cleanup-stale
- Trigger edit form: "Enable session resume" checkbox with explanatory
  inline tooltip.

## Endpoints

- `GET /api/settings/sessions` — global config + storage stats
- `PUT /api/settings/sessions` — update defaults
- `GET /api/sessions` — list active threads with staleness flag
- `DELETE /api/sessions/<id>` — manual reset
- `POST /api/sessions/cleanup-stale` — bulk delete rows older than
  `auto_cleanup_days`

## Schema

Alembic migration `0012_clickup_session_resume.py`:
- ALTER `triggers` ADD `resume_sessions BOOLEAN NOT NULL DEFAULT FALSE`
- CREATE `trigger_session_threads` (id, trigger_id FK, dedup_key,
  claude_session_id, last_used_at, created_at) with UNIQUE
  (trigger_id, dedup_key) and index on (trigger_id, last_used_at).
- Reversible via downgrade().

## Backward compatibility

- All existing triggers keep `resume_sessions=false` (no behaviour change).
- `run_claude` callers unchanged unless they explicitly pass
  `resume_session_id`.
- Schema migration is idempotent (checks `_has_column` / `_has_table`
  before alter/create).

## Cost / quality impact (measured on 86c9kyquv)

Without resume (status quo):
- Oracle 1: 22min, 43k tok, $3.21
- Oracle 2 (re-read everything): 11min, 18k tok, $2.19
- Total: $5.40 + degraded continuity

With resume (projected, prompt cache + reused context):
- Oracle 1: 22min, 43k tok, $3.21
- Oracle 2 (resume): 4min, 8k tok, $0.80
- Total: ~$4.00, 25% saved + full reasoning continuity

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant